import base64
import datetime
from google.appengine.ext import db
from google.appengine.api import memcache

class Tag(db.Model):
  name = db.StringProperty()
  records = db.ListProperty(db.Key)
  
class Record(db.Model):
  date = db.DateTimeProperty()
  dfmt = db.StringProperty()
  value = db.FloatProperty()
  unit = db.StringProperty()
  tags = db.StringListProperty()
  source = db.StringProperty()
  inputtime = db.DateTimeProperty(default = datetime.datetime.now())

class History(db.Model):
  tags = db.StringProperty()
  qcount = db.IntegerProperty(default = 1)
  found = db.BooleanProperty(default = True)
  timestamp = db.DateTimeProperty(default = datetime.datetime.now())

def GenRecordKeyName(date, tags):
  tags.sort()
  return 'r_'+base64.b64encode(" ".join(tags).encode("utf-8")+date.strftime("%Y%m%d%H%M%S"))

def GenTagKeyName(tag):
  return 't_'+base64.b64encode(tag.encode("utf-8"))

def GenHistoryKeyName(tags):
  tags.sort()
  return 'h_'+base64.b64encode(" ".join(tags).encode("utf-8"))

def GenQueryRecordsByTagKey(begin, end, tags):
  tags.sort()
  return 'q_'+base64.b64encode(" ".join(tags).encode("utf-8") + 
                               begin.strftime("%Y%m%d%H%M%S") +
                               end.strftime("%Y%m%d%H%M%S"))

def GenTagBookmark(bookmark, direction, pagesize):
  if "next" == direction \
  or "prev" == direction:
    if bookmark is None:
      return "tbk_"+direction+str(pagesize)+base64.b64encode("FirstBookmark")
    return "tbk_"+direction+str(pagesize)+str(bookmark.key())
  elif "related" == direction:
    bookmark.sort()
    return "tbkr_"+direction+str(pagesize)+"_".join(bookmark)

def GetRecord(date, tags):
  item = memcache.get(GenRecordKeyName(date, tags))
  if item is not None:
    return item
  ins = Record.get_by_key_name(GenRecordKeyName(date, tags))
  if ins is not None:
    memcache.set(GenRecordKeyName(date, tags), (True, ins))
    return (True, ins)
  else:
    memcache.set(GenRecordKeyName(date, tags), (False, None))
  return (False, None)

def GetTag(tag):
  item = memcache.get(GenTagKeyName(tag))
  if item is not None:
    return item
  ins = Tag.get_by_key_name(GenTagKeyName(tag))
  if ins is not None:
    memcache.set(GenTagKeyName(tag), (True, ins))
    return (True, ins)
  else:
    memcache.set(GenTagKeyName(tag), (False, None))
  return (False, None)

def GetHistory(tags):
  item = memcache.get(GenHistoryKeyName(tags))
  if item is not None:
    return item
  ins = History.get_by_key_name(GenHistoryKeyName(tags))
  if ins is not None:
    memcache.set(GenHistoryKeyName(tags), (True, ins))
    return (True, ins)
  else:
    memcache.set(GenHistoryKeyName(tags), (False, None))
  return (False, None)

def UpdateEntity(entities, exist):
  e = dict((k, (exist, v)) for k, v in entities.iteritems()) 
  memcache.set_multi(e)

def DBBatch(op, entities, batch=50):
  start = 0
  while len(entities[start:batch+start]) > 0:
    op(entities[start:batch+start])
    start += batch

def GetRecords(date, begin, end, bookmark, pagesize=100):
  """
  http://google-appengine.googlegroups.com/web/efficient_paging_using_key_instead_of_a_dedicated_unique_property.txt
  *Original starting query* 
  WHERE x > 0 AND x < 9
  *Bookmarkable starting query*
  WHERE x > 0 AND x < 9 ORDER BY x ASC, __key__ ASC
  *Derived queries for starting from bookmark entity B*
  WHERE x = B.x AND __key__ > B ORDER BY __key__ ASC
  WHERE x > B.x AND x < 9 ORDER BY x ASC, __key__ ASC 
  """

  gqls = ["WHERE "+date+">=:1 AND "+date+"<=:2 ORDER BY "+date+" ASC, __key__ ASC",
          "WHERE "+date+"=:1 AND __key__>=:2 ORDER BY __key__ ASC",
          "WHERE "+date+">:1 AND "+date+"<=:2 ORDER BY "+date+" ASC, __key__ ASC"
          ]
  if bookmark is None:
    query = Record.gql(gqls[0], begin, end)
    records = query.fetch(pagesize+1)
  else:
    bookmarkv = {"inputtime": bookmark.inputtime,
                 "date": bookmark.date}
    query1 = Record.gql(gqls[1], bookmarkv[date], bookmark.key())
    query2 = Record.gql(gqls[2], bookmarkv[date], end)
    records = query1.fetch(pagesize+1)
    records += query2.fetch(pagesize+1-len(records))
  if len(records)<pagesize+1:
    return records, None
  else:
    return records[:pagesize], records[-1]
  
def QueryRecordsByTag(begin, end, tags):
  item = memcache.get(GenQueryRecordsByTagKey(begin, end, tags))
  if item is not None:
    return item

  count = 0
  minTag = None
  for tag in tags:
    ins = GetTag(tag)[1]
    if ins is None:
      return []
    if count>len(ins.records) \
    or 0 == count:
      count = len(ins.records)
      minTag = ins
        
  records = Record.get(minTag.records)
  i = 0
  while i<len(records):
    deleted = False
    for tag in tags:
      if tag not in records[i].tags \
      or records[i].date<begin \
      or records[i].date>end:
        del records[i]
        deleted = True
        break
    if False == deleted: i += 1
      
  if len(records)>0:
    memcache.set(GenQueryRecordsByTagKey(begin, end, tags), records)
  return records

def DeleteTagRecords(records):
  tags, deleteTags, updateTags = {}, [], []
  for record in records:
    for tag in record.tags:
      if True == tags.has_key(tag):
        t = tags[tag]
      else:
        t = GetTag(tag)[1]
      if t is None: continue
      if record.key() in t.records:
        t.records.remove(record.key())
      if False == tags.has_key(t.name):
        tags[t.name] = t
  for t in tags.values():
    if 0 == len(t.records):
      deleteTags.append(t)
    else:
      updateTags.append(t)
  DBBatch(db.delete, deleteTags)
  UpdateEntity(dict((GenTagKeyName(k.name), None) for k in deleteTags), False)
  DBBatch(db.put, updateTags)
  UpdateEntity(dict((GenTagKeyName(k.name), k) for k in updateTags), True)
  return len(deleteTags)

def GetTags(bookmark, direction="next", pagesize=10):
  """
  http://google-appengine.googlegroups.com/web/efficient_paging_using_key_instead_of_a_dedicated_unique_property.txt
  *Original starting query*
  SELECT * FROM Foo
  *Bookmarkable starting query*
  SELECT * FROM Foo ORDER BY __key__ ASC
  *Derived queries for starting from bookmark entity B*
  SELECT * FROM Foo WHERE __key__ > B ORDER BY __key__ ASC
  """
  if "next" == direction:
    tags = memcache.get(GenTagBookmark(bookmark, direction, pagesize))
    if tags is not None:
      return tags, tags[-1]
      
    gqls = ["ORDER BY __key__ ASC",
            "WHERE __key__>:1 ORDER BY __key__ ASC",
            ]
    if bookmark is None:
      query = Tag.gql(gqls[0])
      tags = query.fetch(pagesize)
    else:
      query = Tag.gql(gqls[1], bookmark.key())
      tags = query.fetch(pagesize)
    memcache.set(GenTagBookmark(bookmark, direction, pagesize), tags)
    if len(tags)<pagesize:
      return tags, None
    else:
      return tags, tags[-1]
  elif "related" == direction:
    tags = bookmark.split(" ")
    related = memcache.get(GenTagBookmark(tags, direction, pagesize))
    if related is not None:
      return related, None
    records = QueryRecordsByTag(datetime.datetime.strptime("197001010000", "%Y%m%d%H%M%S"),
                      datetime.datetime.strptime("207001010000", "%Y%m%d%H%M%S"),
                      tags)
    related = []
    for record in records:
      related += list(set(record.tags) - set(tags) - set(related))
    memcache.set(GenTagBookmark(tags, direction, pagesize), related)
    return related, None 
  else:
    return [], None